package org.gdstash.db;

import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.gdstash.file.ARZRecord;
import org.gdstash.file.GDParseException;
import org.gdstash.util.GDConstants;
import org.gdstash.util.GDMsgFormatter;
import org.gdstash.util.GDMsgLogger;

import java.sql.*;
import java.util.LinkedList;
import java.util.List;

public class DBEnginePlayer {
    private static final String TABLE_NAME = "GDC_PLAYER";
    private static final int ROW_ID = 1;
    private static final int ROW_BASE_DEX = 2;
    private static final int ROW_BASE_INT = 3;
    private static final int ROW_BASE_STR = 4;
    private static final int ROW_BASE_LIFE = 5;
    private static final int ROW_BASE_MANA = 6;
    private static final int ROW_INCREMENT_DEX = 7;
    private static final int ROW_INCREMENT_INT = 8;
    private static final int ROW_INCREMENT_STR = 9;
    private static final int ROW_INCREMENT_LIFE = 10;
    private static final int ROW_INCREMENT_MANA = 11;
    private static final int ROW_MAX_DEVOTION = 12;
    private static final int ROW_MAX_LEVEL = 13;
    private static final int ROW_XP_FORMULA = 14;
    private static DBEnginePlayer singleton;
    private int baseDex;
    private int baseInt;
    private int baseStr;
    private int baseLife;
    private int baseMana;
    private List<DBEnginePlayerMasteries> skillTrees;
    private int incDex;
    private int incInt;
    private int incStr;
    private int incLife;
    private int incMana;
    private int maxDevotion;
    private int maxLevel;
    private String xpFormula;
    private Expression expression;

    public DBEnginePlayer() {
        this.baseDex = 50;
        this.baseInt = 50;
        this.baseStr = 50;
        this.baseLife = 250;
        this.baseMana = 250;
        this.skillTrees = new LinkedList<DBEnginePlayerMasteries>();
        this.incDex = 8;
        this.incInt = 8;
        this.incStr = 8;
        this.incLife = 20;
        this.incMana = 16;
        this.expression = null;
    }

    private DBEnginePlayer(final ARZRecord record) {
        this.baseDex = record.getPlayerBaseDex();
        this.baseInt = record.getPlayerBaseInt();
        this.baseStr = record.getPlayerBaseStr();
        this.baseLife = record.getPlayerBaseLife();
        this.baseMana = record.getPlayerBaseMana();
        this.skillTrees = record.getMasteryList();
        this.incDex = record.getPlayerIncDex();
        this.incInt = record.getPlayerIncInt();
        this.incStr = record.getPlayerIncStr();
        this.incLife = record.getPlayerIncLife();
        this.incMana = record.getPlayerIncMana();
        this.maxDevotion = record.getPlayerMaxDevotion();
        this.maxLevel = record.getPlayerMaxLevel();
        try {
            this.setXPFormula(record.getXPFormula());
        } catch (GDParseException ex) {
            GDMsgLogger.addError(ex);
        }
    }

    public static void createTable() throws SQLException {
        final String dropTable = "DROP TABLE GDC_PLAYER";
        final String createTable = "CREATE TABLE GDC_PLAYER (ID           VARCHAR(8) NOT NULL, BASE_DEX     INTEGER, BASE_INT     INTEGER, BASE_STR     INTEGER, BASE_LIFE    INTEGER, BASE_MANA    INTEGER, INC_DEX      INTEGER, INC_INT      INTEGER, INC_STR      INTEGER, INC_LIFE     INTEGER, INC_MANA     INTEGER, MAX_DEVOTION INTEGER, MAX_LEVEL    INTEGER, XP_FORMULA   VARCHAR(256), PRIMARY KEY (ID))";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final Statement st = conn.createStatement()) {
                if (GDDBUtil.tableExists(conn, "GDC_PLAYER")) {
                    st.execute(dropTable);
                }
                st.execute(createTable);
                st.close();
                conn.commit();
                DBEnginePlayerMasteries.createTable(conn);
            } catch (SQLException ex) {
                conn.rollback();
                final Object[] args = {"GDC_PLAYER"};
                final String msg = GDMsgFormatter.format(GDMsgFormatter.rbMsg, "ERR_CREATE_TABLE", args);
                GDMsgLogger.addError(msg);
                throw ex;
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    public static void insert(final ARZRecord record) throws SQLException {
        final DBEnginePlayer player = new DBEnginePlayer(record);
        DBEnginePlayer.singleton = null;
        final DBEnginePlayer entry = get();
        if (entry != null) {
            if (entry.maxLevel != 0 && player.maxLevel != 0) {
                return;
            }
            if (entry.baseLife != 0 && player.baseLife != 0) {
                return;
            }
        }
        final boolean levels = record.getFileName().equals("records/creatures/pc/playerlevels.dbr");
        String sql = null;
        if (entry == null) {
            sql = "INSERT INTO GDC_PLAYER VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
        } else if (levels) {
            sql = "UPDATE GDC_PLAYER SET INC_DEX = ?, INC_INT = ?, INC_STR = ?, INC_LIFE = ?, INC_MANA = ?, MAX_DEVOTION = ?, MAX_LEVEL = ?, XP_FORMULA = ? WHERE ID = ?";
        } else {
            sql = "UPDATE GDC_PLAYER SET BASE_DEX = ?, BASE_INT = ?, BASE_STR = ?, BASE_LIFE = ?, BASE_MANA = ? WHERE ID = ?";
        }
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(sql)) {
                if (entry == null) {
                    ps.setString(1, "DEFAULT");
                    ps.setInt(2, player.getBaseDex());
                    ps.setInt(3, player.getBaseInt());
                    ps.setInt(4, player.getBaseStr());
                    ps.setInt(5, player.getBaseLife());
                    ps.setInt(6, player.getBaseMana());
                    ps.setInt(7, player.getIncDex());
                    ps.setInt(8, player.getIncInt());
                    ps.setInt(9, player.getIncStr());
                    ps.setInt(10, player.getIncLife());
                    ps.setInt(11, player.getIncMana());
                    ps.setInt(12, player.getMaxDevotion());
                    ps.setInt(13, player.getMaxLevel());
                    ps.setString(14, player.getXPFormula());
                } else if (levels) {
                    ps.setInt(1, player.getIncDex());
                    ps.setInt(2, player.getIncInt());
                    ps.setInt(3, player.getIncStr());
                    ps.setInt(4, player.getIncLife());
                    ps.setInt(5, player.getIncMana());
                    ps.setInt(6, player.getMaxDevotion());
                    ps.setInt(7, player.getMaxLevel());
                    ps.setString(8, player.getXPFormula());
                    ps.setString(9, "DEFAULT");
                } else {
                    ps.setInt(1, player.getBaseDex());
                    ps.setInt(2, player.getBaseInt());
                    ps.setInt(3, player.getBaseStr());
                    ps.setInt(4, player.getBaseLife());
                    ps.setInt(5, player.getBaseMana());
                    ps.setString(6, "DEFAULT");
                }
                ps.executeUpdate();
                ps.close();
                conn.commit();
                if (!levels) {
                    DBEnginePlayerMasteries.insert(conn, player);
                }
            } catch (SQLException ex) {
                conn.rollback();
                GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_IN_PLAYER_CONFIG"));
                GDMsgLogger.addError(ex);
            } finally {
                conn.setAutoCommit(auto);
            }
        }
    }

    private static List<DBEnginePlayer> wrap(final ResultSet rs) throws SQLException {
        final LinkedList<DBEnginePlayer> list = new LinkedList<DBEnginePlayer>();
        while (rs.next()) {
            final DBEnginePlayer player = new DBEnginePlayer();
            player.baseDex = rs.getInt(2);
            player.baseInt = rs.getInt(3);
            player.baseStr = rs.getInt(4);
            player.baseLife = rs.getInt(5);
            player.baseMana = rs.getInt(6);
            player.incDex = rs.getInt(7);
            player.incInt = rs.getInt(8);
            player.incStr = rs.getInt(9);
            player.incLife = rs.getInt(10);
            player.incMana = rs.getInt(11);
            player.maxLevel = rs.getInt(13);
            player.xpFormula = rs.getString(14);
            if (player.xpFormula != null) {
                player.xpFormula = player.xpFormula.toUpperCase(GDConstants.LOCALE_US);
            }
            try {
                player.setXPFormula(player.xpFormula);
            } catch (GDParseException ex) {
                GDMsgLogger.addError(ex);
            }
            player.skillTrees = DBEnginePlayerMasteries.get();
            list.add(player);
        }
        return list;
    }

    public int getBaseDex() {
        return this.baseDex;
    }

    public int getBaseInt() {
        return this.baseInt;
    }

    public int getBaseStr() {
        return this.baseStr;
    }

    public int getBaseLife() {
        return this.baseLife;
    }

    public int getBaseMana() {
        return this.baseMana;
    }

    public List<DBEnginePlayerMasteries> getMasteryTreeList() {
        return this.skillTrees;
    }

    public boolean containsSkillTreeID(final String id) {
        return this.skillTrees != null && DBEnginePlayerMasteries.containsSkillTreeID(this.skillTrees, id);
    }

    public DBEnginePlayerMasteries retrieveID(final String id) {
        if (this.skillTrees == null) {
            return null;
        }
        return DBEnginePlayerMasteries.retrieveID(this.skillTrees, id);
    }

    public int getIncDex() {
        return this.incDex;
    }

    public int getIncInt() {
        return this.incInt;
    }

    public int getIncStr() {
        return this.incStr;
    }

    public int getIncLife() {
        return this.incLife;
    }

    public int getIncMana() {
        return this.incMana;
    }

    public int getMaxDevotion() {
        return this.maxDevotion;
    }

    public int getMaxLevel() {
        return this.maxLevel;
    }

    public String getXPFormula() {
        return this.xpFormula;
    }

    private void setXPFormula(final String xpFormula) throws GDParseException {
        this.xpFormula = xpFormula;
        if (xpFormula != null) {
            ExpressionBuilder builder = new ExpressionBuilder(xpFormula);
            builder = builder.variables(new String[]{"playerLevel"});
            builder = builder.variables(new String[]{"PLAYERLEVEL"});
            try {
                this.expression = builder.build();
            } catch (Throwable ex) {
                this.expression = null;
                throw new GDParseException(ex.getMessage());
            }
        }
    }

    private void setPlayerLevel(final int level) {
        if (this.expression == null) {
            return;
        }
        this.expression.setVariable("playerLevel", (double) level);
        this.expression.setVariable("PLAYERLEVEL", (double) level);
    }

    private double getPlayerXP() {
        if (this.expression == null) {
            return 0.0;
        }
        final double value = this.expression.evaluate();
        return value;
    }

    public static void delete() throws SQLException {
        final String deleteEntry = "DELETE FROM GDC_PLAYER WHERE ID = ?";
        try (final Connection conn = GDDBData.getConnection()) {
            final boolean auto = conn.getAutoCommit();
            conn.setAutoCommit(false);
            try (final PreparedStatement ps = conn.prepareStatement(deleteEntry)) {
                ps.setString(1, "DEFAULT");
                ps.executeUpdate();
                ps.close();
                DBEnginePlayerMasteries.delete(conn);
                conn.commit();
            } catch (SQLException ex) {
                conn.rollback();
            } finally {
                conn.setAutoCommit(auto);
            }
        } catch (SQLException ex2) {
        }
    }

    public int getPlayerXPByLevel(final int level) {
        int lvl = 1;
        if (level > lvl) {
            lvl = level;
        }
        if (lvl == 1) {
            return 0;
        }
        this.setPlayerLevel(lvl - 1);
        return (int) (this.getPlayerXP() + 0.5);
    }

    public static DBEnginePlayer get() {
        if (DBEnginePlayer.singleton != null) {
            return DBEnginePlayer.singleton;
        }
        final String command = "SELECT * FROM GDC_PLAYER WHERE ID = ?";
        try (final Connection conn = GDDBData.getConnection();
             final PreparedStatement ps = conn.prepareStatement(command)) {
            ps.setString(1, "DEFAULT");
            try (final ResultSet rs = ps.executeQuery()) {
                final List<DBEnginePlayer> list = wrap(rs);
                if (list.isEmpty()) {
                    DBEnginePlayer.singleton = null;
                } else {
                    DBEnginePlayer.singleton = list.get(0);
                }
                conn.commit();
            } catch (SQLException ex) {
                throw ex;
            }
        } catch (SQLException ex2) {
            GDMsgLogger.addError(GDMsgFormatter.getString(GDMsgFormatter.rbMsg, "ERR_READ_CONFIG_PLAYER"));
            GDMsgLogger.addError(ex2);
            DBEnginePlayer.singleton = null;
        }
        return DBEnginePlayer.singleton;
    }

    public int getPlayerLevelByXP(final int xp) {
        int currLevel = 1;
        for (int currXP = 0; currXP < xp; currXP = (int) this.getPlayerXP()) {
            ++currLevel;
            this.setPlayerLevel(currLevel);
        }
        return currLevel;
    }

    public static void reset() {
        DBEnginePlayer.singleton = null;
    }
}
